#include "receiver.h"
#include <QDomDocument>
#include <QNetworkReply>
#include <QStringList>

#include <cstring>

#include "base64/base64.h"

#include <iostream> //debug
using std::cout;
using std::cerr;
using std::endl;

 //Helper Function (Code generation)
	#define getDomNodeValue(parent, name, sname, func) \
		QDomNodeList dom##name = dom##parent.item(0).toElement().elementsByTagName(QString(sname)); \
		if(dom##name .size()) { \
			name = dom##name.item(0).toElement().text()func; }

	#define getDomNodeValue2(parent, name, sname, func) \
		QDomNodeList dom##name = dom##parent.toElement().elementsByTagName(QString(sname)); \
		if(dom##name .size()) { \
			name = dom##name.item(0).toElement().text()func; }

	#define queryAnswer(type,stype) \
		query = xml.elementsByTagName(QString("answer") + QString(stype)); \
    if(query.size()) \
			return answer##type(input)

namespace Avd {

/**
 * <answerLogin>
 *   <status>0</status>
 *   <session>983493</session>
 * </answerLogin>
 */

void Receiver::answerLogin(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	int session = 0;
	QDomDocument xml;
	xml.setContent(input);
	QDomNodeList domAnswerLogin = xml.elementsByTagName("answerLogin");
	if(domAnswerLogin.size()) {
		QDomNodeList domStatus = domAnswerLogin.item(0).toElement().elementsByTagName("status");
		if(domStatus.size()) {
			status = domStatus.item(0).toElement().text().toInt();
			session = domStatus.item(0).toElement().text().toInt();
			getDomNodeValue(AnswerLogin, status, "status", .toInt()); // <status>
			getDomNodeValue(AnswerLogin, session, "session", .toInt()); // <session>
			emit sigLogin(status, session);
			return;
		}
	}
	throw ReceiverError("void Receiver::answerLogin(const QString& input): reading XML.", input);
}

/**
 * <answerLogoff>
 * 	<status>0</status>
 * </answerLogoff>
 */

void Receiver::answerLogoff(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QDomDocument xml;
	xml.setContent(input);
	QDomNodeList domAnswerLogoff = xml.elementsByTagName("answerLogoff");
	if(domAnswerLogoff.size()) {
		QDomNodeList domStatus = domAnswerLogoff.item(0).toElement().elementsByTagName("status");
		if(domStatus.size()) {
			status = domStatus.item(0).toElement().text().toInt();
			emit sigLogoff(status);
			return;
		}
	}
	throw ReceiverError("XML exception occured: void Receiver::answerLogoff(const QString& input): reading XML.", input);
}

/**
 *  <answerDownload>
 *    <file> 
 *      <name>texto</name>
 *      <path>/home/thiago/</path>
 *      <status>0</status>
 *    </file>
 *  </answerDownload>
 */

void Receiver::answerDownload(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	long long version = 0;
	long long part = 0;
	long long totalParts = 0;
	QString name;
	QString path;
	QString data;

	QDomDocument xml;
	xml.setContent(input);
	// <answerDownload>
	QDomNodeList domAnswerDownload = xml.elementsByTagName("answerDownload");
	
	if(domAnswerDownload.size()) {
		getDomNodeValue(AnswerDownload, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerDownload, name, "name",); // <name>
		getDomNodeValue(AnswerDownload, path, "path",); // <path>
		getDomNodeValue(AnswerDownload, data, "data",); // <data>
		getDomNodeValue(AnswerDownload, part, "part", .toInt()); // <part>
		getDomNodeValue(AnswerDownload, totalParts, "totalParts", .toInt()); // <totalParts>
		getDomNodeValue(AnswerDownload, version, "version", .toInt()); // <version>

		if(name.isEmpty())
			throw ReceiverError("void Receiver::answerDownload(const QString& input): <name> is empty.", input);
		if(version <= 0)
			throw ReceiverError("void Receiver::answerDownload(const QString& input): <version> is invalid.", input);
		if(part > totalParts)
			throw ReceiverError("void Receiver::answerDownload(const QString& input): <part> is invalid.", input);

		string stringData = data.toStdString();
		const char* src = stringData.c_str();

		int srclen = data.size();

		char* dest = new char[modp_b64w_decode_len(srclen)];
		int len = modp_b64w_decode(dest, src, srclen);
		if (len == -1) {
			delete[] dest;
			throw ReceiverError("void Receiver::answerDownload(const QString& input): data decoding error.", input);
		}
		
		File file(name, path, modp_b64w_decode_len(srclen) - 2, version);
		file.setData(dest, modp_b64w_decode_len(srclen) - 2);
		delete[] dest;
		if(part != 0 && totalParts != 0) {
			emit sigDownload(status, file, part, totalParts);
		} else {
			emit sigDownload(status, file);
		}
		return;
	}
	throw ReceiverError("void Receiver::answerDownload(const QString& input): reading XML.", input);
}

/**
 * <answerUpload>
 * 	 <path>/documentos</path>
 * 	 <name>texto</name>
 * 	 <version>4</version>
 * 	 <status>0</status>
 * </answerUpload>
 */

void Receiver::answerUpload(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString name;
	QString path;
	unsigned int version = 0;
	
	QDomDocument xml;
	xml.setContent(input);
	// <AnswerUpload>
	QDomNodeList domAnswerUpload = xml.elementsByTagName("answerUpload");
	
	if(domAnswerUpload.size()) {
		getDomNodeValue(AnswerUpload, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerUpload, name, "name",); // <name>
		getDomNodeValue(AnswerUpload, path, "path",); // <path>
		getDomNodeValue(AnswerUpload, version, "version", .toInt()); // <version>
		if(name.isEmpty())
			throw ReceiverError("void Receiver::answerUpload(const QString& input): <name> is empty.", input);

		File file(name, path, 0, version);
		emit sigUpload(status, file);
		return;
	}
	
	throw ReceiverError("void Receiver::answerUpload(const QString& input): reading XML.", input);
}

/**
 * <answerList>
 * 	<path>/documentos</path>
 * 	<file> 
 * 		<name>texto</name>
 * 		<size>6</size>
 * 		<version>1</version>
 * 	</file>
 * 	<file> 
 * 		<name>texto2</name>
 * 		<size>7</size>
 * 		<version>256</version>
 * 	</file>
 * 	<folder>
 * 		<name>importantes</name>
 * 		<size>100000001</size>
 * 		<itens>45</itens>
 * 	</folder>
 * 	<status>0</status>
 * </answerList>
 */

void Receiver::answerList(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString path;
	QList<File> files;
	QList<Folder> folders;
	QString name;
	unsigned int size = 0;
	unsigned int version = 0;
	unsigned int itens = 0;

	QDomDocument xml;
	xml.setContent(input);
	// <answerList>
	QDomNodeList domAnswerList = xml.elementsByTagName("answerList");
	if(domAnswerList.size()) {
		getDomNodeValue(AnswerList, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerList, path, "path", ); // <path>

		QDomNodeList domFiles = domAnswerList.item(0).toElement().elementsByTagName("file");
		int numberOfFiles = domFiles.size();
		while(numberOfFiles--) {
			name = QString();
			QDomNode domFile = domFiles.item(numberOfFiles);
			getDomNodeValue2(File, name, "name",); // <name>
			getDomNodeValue2(File, size, "size", .toInt()); // <size>
			getDomNodeValue2(File, version, "version", .toInt()); // <version>
			if(!name.isEmpty())
				files.push_back(File(name, path, size, version));
		}
		
		QDomNodeList domFolders = domAnswerList.item(0).toElement().elementsByTagName("folder");
		int numberOfFolders = domFolders.size();
		while(numberOfFolders--) {
			name = QString();
			QDomNode domFolder = domFolders.item(numberOfFolders);
			getDomNodeValue2(Folder, name, "name",.trimmed()); // <name>
			getDomNodeValue2(Folder, size, "size", .toInt()); // <size>
			getDomNodeValue2(Folder, itens, "itens", .toInt()); // <itens>
			if(!name.isEmpty())
				folders.push_back(Folder(name, path, "", itens));
		}
		
		emit sigList(status, path, files, folders);
		return;
	}
	throw ReceiverError("void Receiver::answerList(const QString& input): reading XML.", input);
}

/**
 * <answerMove>
 *   <oldPath>/videos</oldPath>
 *   <oldName>video</oldName>
 *	 <newPath>/videos/tutoriais</newPath>
 *	 <newName>videoSD</newName>
 *	 <status>0</status>
 * </answerMove>
 */

void Receiver::answerMove(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString oldPath;
	QString oldName;
	QString newPath;
	QString newName;

	cout << input.toStdString() << endl;
	QDomDocument xml;
	xml.setContent(input);
	// <answerMovef>
	QDomNodeList domAnswerMove = xml.elementsByTagName("answerMove");

	if(domAnswerMove.size()) {
		getDomNodeValue(AnswerMove, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerMove, oldPath, "oldPath",); // <oldPath>
		getDomNodeValue(AnswerMove, oldName, "oldName",); // <oldName>
		getDomNodeValue(AnswerMove, newPath, "newPath",); // <newPath>
		getDomNodeValue(AnswerMove, newName, "newName",); // <newName>

		emit sigMove(status,oldName + "/" + oldPath, newName + "/" + newPath);
		return;
	}

	throw ReceiverError("void Receiver::answerMove(const QString& input): reading XML.", input);
}

/**
 * <answerDelete>
 * 	<path>/documentos</path>
 * 	<name>texto</name>
 * 	<status>0</status>
 * </answerDelete>
 */

void Receiver::answerDelete(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString name;
	QString path;
	
	QDomDocument xml;
	xml.setContent(input);
	// <answerDelete>
	QDomNodeList domAnswerDelete = xml.elementsByTagName("answerDelete");
	
	if(domAnswerDelete.size()) {
		getDomNodeValue(AnswerDelete, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerDelete, name, "name",); // <name>
		getDomNodeValue(AnswerDelete, path, "path",); // <path>
		QString fullPath = path + "/" + name;
		emit sigDelete(status, fullPath);
		return;
	}
	
	throw ReceiverError("void Receiver::answerDelete(const QString& input): reading XML.", input);
}

/**
 * <answerCreateFolder>
 * 	<path>/documentos</path>
 * 	<name>legais</name>
 * 	<status>1</status>
 * </answerCreateFolder>
 */

void Receiver::answerCreateFolder(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString name;
	QString path;

	QDomDocument xml;
	xml.setContent(input);
	// <answerMkdir>
	QDomNodeList domAnswerCreateFolder = xml.elementsByTagName("answerCreateFolder");

	if(domAnswerCreateFolder.size()) {
		getDomNodeValue(AnswerCreateFolder, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerCreateFolder, name, "name",); // <name>
		getDomNodeValue(AnswerCreateFolder, path, "path",); // <path>

		emit sigCreateFolder(status, Folder(name, path));
		return;
	}

	throw ReceiverError("void Receiver::answerCreateFolder(const QString& input): reading XML.", input);
}

/**
 * <answerCopy>
 * 	<path>/documentos</path>
 * 	<name>texto</name>
 * 	<copyPath>/documentos/importantes</copyPath>
 * 	<status>0</status>
 * </answerCopy>
 */

void Receiver::answerCopy(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString name;
	QString path;
	QString copyPath;
	
	QDomDocument xml;
	xml.setContent(input);
	// <answerCopy>
	QDomNodeList domAnswerCopy = xml.elementsByTagName("answerCopy");
	
	if(domAnswerCopy.size()) {
		getDomNodeValue(AnswerCopy, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerCopy, name, "name",); // <name>
		getDomNodeValue(AnswerCopy, path, "path",); // <path>
		getDomNodeValue(AnswerCopy, copyPath, "copyPath",); // <copyPath>
		
		emit sigCopy(status, copyPath, File(name, path, 0, 0/*version*/));
		return;
	}

	throw ReceiverError("void Receiver::answerCopy(const QString& input): reading XML.", input);
}

/**
 * <answerVersion>
 * 	<path>/documentos</path>
 * 	<name>texto</name>
 * 	<version>4</version>
 * 	<status>0</status>
 * </answerVersion>
 */

void Receiver::answerVersion(const QString& input) throw(ReceiverError) {
	unsigned int status = 0;
	QString name;
	QString path;
	unsigned int version = 0;

	QDomDocument xml;
	xml.setContent(input);
	// <answerVersion>
	QDomNodeList domAnswerVersion = xml.elementsByTagName("answerVersion");

	if(domAnswerVersion.size()) {
		getDomNodeValue(AnswerVersion, status, "status", .toInt()); // <status>
		getDomNodeValue(AnswerVersion, name, "name",); // <name>
		getDomNodeValue(AnswerVersion, path, "path",); // <path>
		getDomNodeValue(AnswerVersion, version, "version", .toInt()); // <version>

		emit sigVersion(status, File(name, path, 0, version));
		return;
	}

	throw ReceiverError("void Receiver::answerVersion(const QString& input): reading XML.", input);
}


void Receiver::receive(QNetworkReply *reply) throw(ReceiverError) {
	QByteArray data = reply->readAll();
	QString input(data);
	QDomDocument xml;
	xml.setContent(input);
	QDomNodeList query;
	cout << "Receiving " << input.toStdString() << endl;
	try {
		queryAnswer(Login, "Login");
		queryAnswer(Logoff, "Logoff");
		queryAnswer(Upload, "Upload");
		queryAnswer(List, "List");
		queryAnswer(Delete, "Delete");
		queryAnswer(Version, "Version");
		queryAnswer(Copy, "Copy");
		queryAnswer(CreateFolder,"CreateFolder");
		queryAnswer(Download,"Download");
	}
	catch(ReceiverError& e) {
		cerr << "ReceiverError exception occured: " << e.what() << endl;
		cerr << input.toStdString() << endl;
		return;
	}
	throw ReceiverError("void Receiver::receive(QNetworkReply* reply): Unknown XML reply.", input);
}

}
